﻿/*
 * The MIT License (MIT)
 * 
 * Copyright (c) [2025] [wuzjjj]
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;

using System.Text.RegularExpressions;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Management;

namespace 串口通信
{
    public partial class Form1 : Form
    {
        private enum HardwareEnum
        {
            // 硬件
            Win32_Processor, // CPU 处理器
            Win32_PhysicalMemory, // 物理内存条
            Win32_Keyboard, // 键盘
            Win32_PointingDevice, // 点输入设备，包括鼠标。
            Win32_FloppyDrive, // 软盘驱动器
            Win32_DiskDrive, // 硬盘驱动器
            Win32_CDROMDrive, // 光盘驱动器
            Win32_BaseBoard, // 主板
            Win32_BIOS, // BIOS 芯片
            Win32_ParallelPort, // 并口
            Win32_SerialPort, // 串口
            Win32_SerialPortConfiguration, // 串口配置
            Win32_SoundDevice, // 多媒体设置，一般指声卡。
            Win32_SystemSlot, // 主板插槽 (ISA & PCI & AGP)
            Win32_USBController, // USB 控制器
            Win32_NetworkAdapter, // 网络适配器
            Win32_NetworkAdapterConfiguration, // 网络适配器设置
            Win32_Printer, // 打印机
            Win32_PrinterConfiguration, // 打印机设置
            Win32_PrintJob, // 打印机任务
            Win32_TCPIPPrinterPort, // 打印机端口
            Win32_POTSModem, // MODEM
            Win32_POTSModemToSerialPort, // MODEM 端口
            Win32_DesktopMonitor, // 显示器
            Win32_DisplayConfiguration, // 显卡
            Win32_DisplayControllerConfiguration, // 显卡设置
            Win32_VideoController, // 显卡细节。
            Win32_VideoSettings, // 显卡支持的显示模式。

            // 操作系统
            Win32_TimeZone, // 时区
            Win32_SystemDriver, // 驱动程序
            Win32_DiskPartition, // 磁盘分区
            Win32_LogicalDisk, // 逻辑磁盘
            Win32_LogicalDiskToPartition, // 逻辑磁盘所在分区及始末位置。
            Win32_LogicalMemoryConfiguration, // 逻辑内存配置
            Win32_PageFile, // 系统页文件信息
            Win32_PageFileSetting, // 页文件设置
            Win32_BootConfiguration, // 系统启动配置
            Win32_ComputerSystem, // 计算机信息简要
            Win32_OperatingSystem, // 操作系统信息
            Win32_StartupCommand, // 系统自动启动程序
            Win32_Service, // 系统安装的服务
            Win32_Group, // 系统管理组
            Win32_GroupUser, // 系统组帐号
            Win32_UserAccount, // 用户帐号
            Win32_Process, // 系统进程
            Win32_Thread, // 系统线程
            Win32_Share, // 共享
            Win32_NetworkClient, // 已安装的网络客户端
            Win32_NetworkProtocol, // 已安装的网络协议
            Win32_PnPEntity,//all device
        }
        public enum CMD                                             //命令,用于和MCU通信,以及工作线程识别当前任务
        {
            GETINFO,
            ERASE_CHIP,
            ERASE,
            SAVE,
            READ,
        }

        public enum RESULT                                          //状态值,用于判断MCU返回的状态是否成功
        {
            OK,
            ERR,
        }

        public enum STATUS                                           //错误值,用于判断函数返回值,类似C的errno
        {
            OK = 0,
            TIMEOUT = -1,
            CRC16 = -2,
            SERIAL = -3,
            NOVAL = -4,
        }

        public struct InfoFont
        {
            public UInt32 Addr { get; set; }
            public UInt32 Size { get; set; }
            public InfoFont(UInt32 addr, UInt32 size)
            {
                Addr = addr;
                Size = size;
            }
            public byte[] ToByteArray()
            {
                int size = Marshal.SizeOf(this);
                byte[] bytes = new byte[size];
                IntPtr ptr = Marshal.AllocHGlobal(size);
                try
                {
                    Marshal.StructureToPtr(this, ptr, true);
                    Marshal.Copy(ptr, bytes, 0, size);
                }
                finally
                {
                    Marshal.FreeHGlobal(ptr);
                }
                return bytes;
            }
        }

        public struct HeaderFont
        {
            public byte Header { get; set; }
            public InfoFont[] InfoFont;
            public byte[] ToByteArray()
            {
                int sizeInfoFont = InfoFont.Length;
                int totalSize = Marshal.SizeOf(Header) +
                                (sizeInfoFont * Marshal.SizeOf(typeof(InfoFont)));

                byte[] buffer = new byte[totalSize];
                int offset = 0;

                // Copy Header
                Buffer.BlockCopy(BitConverter.GetBytes(Header), 0, buffer, offset, Marshal.SizeOf(Header));
                offset += Marshal.SizeOf(Header);

                // Copy InfoFont
                for (int i = 0; i < sizeInfoFont; i++)
                {
                    byte[] infoFontBytes = InfoFont[i].ToByteArray();
                    Buffer.BlockCopy(infoFontBytes, 0, buffer, offset, infoFontBytes.Length);
                    offset += infoFontBytes.Length;
                }

                return buffer;
            }
            public int Length()
            {
                int sizeInfoFont = InfoFont.Length;
                int totalSize = Marshal.SizeOf(Header) +
                                (sizeInfoFont * Marshal.SizeOf(typeof(InfoFont)));
                return totalSize;
            }
        }

        public struct Header
        {
            public UInt16 header { get; set; }
            public UInt16 cmd { get; set; }
            public UInt32 len { get; set; }
        }

        public struct end_t
        {
            public UInt16 crc16 { get; set; }
            public UInt16 end { get; set; }
        }

        public struct CmdEarse
        {
            public UInt32 Addr { get; set; }
            public UInt32 Len { get; set; }
        }

        public struct HeaderSave
        {
            public UInt32 Addr { get; set; }
            public UInt32 Len { get; set; }
        }

        public struct CmdRead
        {
            public UInt32 Addr { get; set; }
            public UInt32 Len { get; set; }
        }

        public struct RspInfo
        {
            public UInt32 Id { get; set; }
            public UInt32 Size { get; set; }
        }

        public struct RspStatus
        {
            public UInt32 Addr { get; set; }
            public UInt32 Status { get; set; }
        }

        SerialPort sp = new SerialPort();                                           //串口
        readonly string filePathSaveBin = "E:\\资料\\Air\\Air32F103\\字库W25Q128\\新建文件夹\\save.bin"; // 文件路径
        readonly string filePathReadBin = "E:\\资料\\Air\\Air32F103\\字库W25Q128\\新建文件夹\\read.bin"; // 文件路径
        readonly UInt32 addrFlashStart = 0x00C00000;                                //字库起始地址,固定为0x00C00000
        private static bool thread_running = false;                                 //线程是否处于运行中,防止重复进入工作线程
        const UInt32 MAX_RETRY = 5;                                                     //最大重试次数


        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Refresh_COM();

            //波特率
            cmbBaudRate.Items.Add("4800");
            cmbBaudRate.Items.Add("9600");
            cmbBaudRate.Items.Add("19200");
            cmbBaudRate.Items.Add("38400");
            cmbBaudRate.Items.Add("57600");
            cmbBaudRate.Items.Add("115200");
            cmbBaudRate.Items.Add("230400");
            cmbBaudRate.Items.Add("460800");
            cmbBaudRate.Items.Add("921600");
            cmbBaudRate.SelectedIndex = 8;

            labStatus.Text = "端口未打开";

            txtAddr1.Text = "0x00000000";
            txtAddr2.Text = "0x00000000";
            txtAddr3.Text = "0x00000000";
            txtAddr4.Text = "0x00000000";

            txtFile1.Text = "E:\\资料\\Air\\Air32F103\\字库W25Q128\\新建文件夹\\UNIGBK.BIN";
            txtFile2.Text = "E:\\资料\\Air\\Air32F103\\字库W25Q128\\新建文件夹\\GBK12.FON";
            txtFile3.Text = "E:\\资料\\Air\\Air32F103\\字库W25Q128\\新建文件夹\\GBK16.FON";
            txtFile4.Text = "E:\\资料\\Air\\Air32F103\\字库W25Q128\\新建文件夹\\GBK24.FON";

            txtFileSave.Text = filePathSaveBin;
            txtFileRead.Text = filePathReadBin;
            txtStart.Text = "0x" + addrFlashStart.ToString("X8");
            FileInfo fileinfo = new FileInfo(filePathSaveBin);
            txtLength.Text = "0x" + ((UInt32)fileinfo.Length).ToString("X8");
            prbSend.Maximum = 1000;
            prbRead.Maximum = 1000;
        }

        //将结构体转换为字节数组的通用方法
        public static byte[] StructToByteArray<T>(T data) where T : struct
        {
            int size = Marshal.SizeOf<T>();                                             //获取结构体的大小
            byte[] byteArray = new byte[size];                                          //创建一个与结构体大小相同的字节数组

            unsafe                                                                      //使用unsafe上下文进行内存复制
            {
                fixed (byte* bytePtr = byteArray)
                {
                    Marshal.StructureToPtr(data, (IntPtr)bytePtr, false);               //将结构体数据复制到字节数组中
                }
            }

            return byteArray;                                                           //返回数组
        }

        //将字节数组转换为结构体的通用方法
        public static T ByteArrayToStruct<T>(byte[] byteArray) where T : struct
        {

            int size = Marshal.SizeOf<T>();                                             //获取结构体的大小
            T structure;                                                                //创建一个新的结构体实例
            IntPtr ptr = Marshal.AllocHGlobal(size);                                    //分配结构体的内存

            try
            {
                Marshal.Copy(byteArray, 0, ptr, size);                                  //将字节数组复制到内存中
                structure = Marshal.PtrToStructure<T>(ptr);                             // 将内存中的数据转换为结构体
            }
            finally
            {
                Marshal.FreeHGlobal(ptr);                                               //释放内存
            }

            return structure;                                                           //返回结构体
        }

        private void btnRefresh_Click(object sender, EventArgs e)
        {
            Refresh_COM();
        }

        private void btnSwitchSP_Click(object sender, EventArgs e)
        {
            if (btnSwitchSP.Text == "关闭串口")
            {
                sp.Close();
                labStatus.Text = "端口未打开";
                btnSwitchSP.Text = "打开串口";
                return;
            }

            if (cmbPort.Text == "" || cmbBaudRate.Text == "")
            {
                MessageBox.Show("请先设置串口！", "RS232串口通信");
                labStatus.Text = "端口未打开";
                btnSwitchSP.Text = "打开串口";
                return;
            }

            string pattern = @"COM\d+";
            Match match = Regex.Match(cmbPort.Text, pattern);
            if (!match.Success)
                return;

            sp.PortName = match.Value;
            sp.BaudRate = int.Parse(cmbBaudRate.Text);
            sp.DataBits = 8;
            sp.StopBits = StopBits.One;
            sp.Parity = Parity.None;
            sp.ReadTimeout = 500;

            try
            {
                if (sp.IsOpen)
                {
                    sp.Close();
                    sp.Open();
                }
                else
                {
                    sp.Open();
                }
                sp.Encoding = System.Text.Encoding.GetEncoding("GB2312");//GB2312即信息交换用汉字编码字符集.
                sp.DiscardInBuffer();//清空接收缓冲
                                     //timer1.Enabled = true;
                btnSwitchSP.Text = "关闭串口";
                labStatus.Text = "端口号：" + sp.PortName +
                                 " | 波特率：" + sp.BaudRate +
                                 " | 数据位：" + sp.DataBits +
                                 " | 停止位：" + (int)sp.StopBits +
                                 " | 校验位：" + (int)sp.Parity;
            }
            catch (Exception ex)
            {
                MessageBox.Show("错误：" + ex.Message, "C#串口通信");
                labStatus.Text = "端口未打开";
                btnSwitchSP.Text = "打开串口";
            }


        }

        private void btnOpen1_Click(object sender, EventArgs e)
        {
            txtFile1.Text = OpenFile();
        }

        private void btnOpen2_Click(object sender, EventArgs e)
        {
            txtFile2.Text = OpenFile();
        }

        private void btnOpen3_Click(object sender, EventArgs e)
        {
            txtFile3.Text = OpenFile();
        }

        private void btnOpen4_Click(object sender, EventArgs e)
        {
            txtFile4.Text = OpenFile();
        }

        private string OpenFile()                                                       //打开文件对话框选择文件
        {
            string filePath = "";

            openFileDialog1.Title = "选择文件";                                         //设置对话框的标题和过滤器
            openFileDialog1.Filter = "所有文件 (*.*)|*.*";

            if (openFileDialog1.ShowDialog() == DialogResult.OK)                        //如果用户成功选择了文件
            {

                filePath = openFileDialog1.FileName;                                    //获取选定文件的路径
            }
            return filePath;
        }

        //获取设备管理器设备信息
        private static string[] MulGetHardwareInfo(HardwareEnum hardType, string propKey)
        {
            List<string> strs = new List<string>();
            try
            {
                using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("select * from " + hardType))
                {
                    var hardInfos = searcher.Get();
                    foreach (var hardInfo in hardInfos)
                    {
                        if (hardInfo.Properties[propKey].Value != null && hardInfo.Properties[propKey].Value.ToString().Contains("COM"))
                        {
                            strs.Add(hardInfo.Properties[propKey].Value.ToString());
                        }

                    }
                    searcher.Dispose();
                }

                return strs.ToArray();
            }
            catch
            {
                return strs.ToArray();
            }
        }

        private void Refresh_COM()                                                      //刷新COM口信息
        {
            cmbPort.Items.Clear();                                                      //清除当前com口下拉框的已有信息
            string[] ports = MulGetHardwareInfo(HardwareEnum.Win32_PnPEntity, "Name");  //获取当前设备信息
            //string[] ports = SerialPort.GetPortNames();                                //这种方式获取的COM口不含设备名
            foreach (string port in ports)                                              //将设备添加到com口下拉框
            {
                cmbPort.Items.Add(port);
            }
            cmbPort.SelectedIndex = 0;                                                  //默认选择第一个
        }

        private bool checkSerialPortConnected()                                         //检查串口是否连接,在每个串口操作前检查,避免报错
        {
            if (!sp.IsOpen)
            {
                MessageBox.Show("请先打开串口！");
                return false;
            }
            return true;
        }

        public static UInt16 CalculateCRC16(byte[] data, UInt32 size)                   //CRC16校验
        {
            UInt16 wCrc = 0xFFFF;
            for (int i = 0; i < size; i++)
            {
                wCrc ^= Convert.ToUInt16(data[i]);
                for (int j = 0; j < 8; j++)
                {
                    if ((wCrc & 0x0001) == 1)
                    {
                        wCrc >>= 1;
                        wCrc ^= 0xA001;//异或多项式
                    }
                    else
                    {
                        wCrc >>= 1;
                    }
                }
            }
            return wCrc;
        }

        UInt32 String2UInt32(string input)                                              //字符串转为Uint32类型数值,一般是地址需要进行转换
        {
            UInt32 value = 0;
            if (input.StartsWith("0x", StringComparison.OrdinalIgnoreCase))             //如果是0x开头的,按照16进制解析
            {
                value = UInt32.Parse(input.Substring(2), System.Globalization.NumberStyles.HexNumber);
            }
            else                                                                        //不是0x开头,按照10进制解析
            {
                value = UInt32.Parse(input);
            }
            return value;
        }

        private int ReadFileBuffer(string filePath, byte[] buffer, int offset)          //读文件到buffer
        {
            int bytesRead = 0;
            if (string.IsNullOrWhiteSpace(filePath))                                    //文件名为空,直接返回
                return 0;

            try
            {
                using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
                {
                    bytesRead = fileStream.Read(buffer, offset, buffer.Length - offset);
                }
            }
            catch (IOException ex)
            {
                // 处理文件读取错误
                MessageBox.Show("读文件错误: " + ex.Message);
            }

            return bytesRead;
        }

        private void GenerFlashData(string savePath, string[] files)                    //生成字库flash数据,包括帧头、各字库文件拼接
        {
            if (files.Length == 0)                                                      //字库文件名列表无字库
            {
                MessageBox.Show("字库列表为空");
                return;
            }

            //第一步:根据字库的数量定义字库帧头
            UInt16 size_font = (UInt16)files.Length;
            HeaderFont headerFont = new HeaderFont
            {
                Header = 0xAA,
                InfoFont = new InfoFont[size_font] //初始化数组
            };

            //第二步:读取并计算各字库的信息(地址和长度),填充到帧头的字库信息中
            UInt32 size_header = (UInt32)headerFont.Length();                           //帧头长度
            long sizes = size_header;                                                   //总数据长度为帧头+各字库长度
            UInt32 addrFlashStart = (UInt32)String2UInt32(txtStart.Text);               //flash保存字库信息的起始地址
            UInt32 addr = addrFlashStart + size_header;                                 //数据的地址 = flash起始地址 + 帧头
            try
            {
                for (int i = 0; i < size_font; i++)                                     //计算各字库长度
                {
                    FileInfo fileinfo = new FileInfo(files[i]);
                    headerFont.InfoFont[i] = new InfoFont(addr, (UInt32)fileinfo.Length);
                    sizes += fileinfo.Length;                                           //字库总长度累加
                    addr += (UInt32)fileinfo.Length;                                    //地址累加
                }
            }
            catch (IOException ex)
            {
                MessageBox.Show("读文件错误: " + ex.Message);
                return;
            }

            //第三步:定义总字库数据的buffer,并拷贝帧头,读取字库数据
            byte[] buffer = new byte[sizes];                                            //用于保存所有数据的buffer
            int offset = (int)size_header;                                              //当前偏移位置,用于读取字库到指定位置
            Buffer.BlockCopy(headerFont.ToByteArray(), 0, buffer, 0, headerFont.Length()); //字库信息拷贝到buffer
            for (int i = 0; i < files.Length; i++)
            {
                offset += ReadFileBuffer(files[i], buffer, offset);                     //将各个字库文件读到buffer指定offset的位置
            }

            //第四步:将buffer写到文件
            File.WriteAllBytes(savePath, buffer);                                       //将buffer写入到文件中
            Process.Start("explorer.exe", Path.GetDirectoryName(savePath));             //在资源管理器中打开保存的文件夹路径

            labStatus.Text = "生成成功: " + sizes + "字节";                             //状态栏显示

            if (files.Length >= 4)                                                      //各字库的起始地址显示到地址文本框
                txtAddr4.Text = "0x" + headerFont.InfoFont[3].Addr.ToString("X8");
            if (files.Length >= 3)
                txtAddr3.Text = "0x" + headerFont.InfoFont[2].Addr.ToString("X8");
            if (files.Length >= 2)
                txtAddr2.Text = "0x" + headerFont.InfoFont[1].Addr.ToString("X8");
            if (files.Length >= 1)
                txtAddr1.Text = "0x" + headerFont.InfoFont[0].Addr.ToString("X8");

            txtStart.Text = "0x" + addrFlashStart.ToString("X8");                       //字库起始地址文本框
            txtLength.Text = "0x" + ((UInt32)sizes).ToString("X8");                     //字库长度
        }

        private void btnGenerPath_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();                       //保存文件窗口

            saveFileDialog.Filter = "二进制文件|*.bin|文本文件|*.txt|所有文件|*.*";     //需要筛选的文件类型
            saveFileDialog.Title = "生成的文件保存为";                                  //对话框的标题
            saveFileDialog.FileName = "save.bin";                                       //设置初始文件名

            if (!string.IsNullOrWhiteSpace(txtFileSave.Text))                           //如果当前已存在路径,直接打开路径文件夹
            {
                string curPath = Path.GetDirectoryName(txtFileSave.Text);               //获取当前文件名的路径
                saveFileDialog.InitialDirectory = curPath;                              //设置文件对话框的初始路径
            }

            if (saveFileDialog.ShowDialog() == DialogResult.OK)                         //如果用户选择了文件夹，并点击了确定
            {
                string filePath = saveFileDialog.FileName;                              //获取选中文件夹的路径
                txtFileSave.Text = filePath;                                            //设置保存文件路径名
            }
        }

        private void btnGener_Click(object sender, EventArgs e)
        {
            string savePath = txtFileSave.Text;                                         //字库保存路径
            if (string.IsNullOrWhiteSpace(savePath))                                    //保存文件路径为空,返回
            {
                MessageBox.Show("文件保存地址为空");
                return;
            }

            List<string> files = new List<string>();                                    //字库列表

            if (!string.IsNullOrEmpty(txtFile1.Text))                                   //依次将各个字库添加到字库列表
                files.Add(txtFile1.Text);
            if (!string.IsNullOrEmpty(txtFile2.Text))
                files.Add(txtFile2.Text);
            if (!string.IsNullOrEmpty(txtFile3.Text))
                files.Add(txtFile3.Text);
            if (!string.IsNullOrEmpty(txtFile4.Text))
                files.Add(txtFile4.Text);

            GenerFlashData(savePath, files.ToArray());                                  //将各字库添加帧头后保存到指定路径
        }

        private void btnReadPath_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();

            saveFileDialog.Filter = "二进制文件|*.bin|文本文件|*.txt|所有文件|*.*";     //需要筛选的文件类型
            saveFileDialog.Title = "从FLASH读取的文件保存为";                           //对话框的标题
            saveFileDialog.FileName = "read.bin";                                       //设置初始文件名

            if (!string.IsNullOrWhiteSpace(txtFileRead.Text))                           //如果当前已存在路径,直接打开路径文件夹
            {
                string curPath = Path.GetDirectoryName(txtFileRead.Text);               //获取当前文件名的路径
                saveFileDialog.InitialDirectory = curPath;                              //设置文件对话框的初始路径
            }

            if (saveFileDialog.ShowDialog() == DialogResult.OK)                         //如果用户选择了文件夹，并点击了确定
            {
                string filePath = saveFileDialog.FileName;                              //获取选中文件夹的路径
                txtFileRead.Text = filePath;                                            //设置读取文件路径名
            }
        }

        private STATUS SerialPort_Send_data(byte[] data)                                  //串口发送数据
        {
            if (!checkSerialPortConnected())                                            //串口异常,直接返回
                return STATUS.SERIAL;

            try
            {
                sp.Write(data, 0, data.Length);                                         //串口发送数据
            }
            catch (Exception ex)                                                        //发送失败,返回
            {
                MessageBox.Show("错误：" + ex.Message);
                return STATUS.SERIAL;
            }
            return STATUS.OK;
        }

        //通用发送数据函数发送命令,前面添加头,后面添加尾,包含CRC16校验功能
        //参数:发送数据cmd、数据
        private STATUS SendData(CMD cmd, byte[] data)
        {
            Header header = new Header();                                           //帧头
            end_t end = new end_t();                                                    //帧尾
            UInt16 len = (UInt16)(Marshal.SizeOf(typeof(Header)) +                    //总数据包长度
                                  Marshal.SizeOf(typeof(end_t)) +
                                  data.Length);
            byte[] buffer = new byte[len];                                              //buffer,用于保存帧头、数据、帧尾

            header.header = 0xAABB;                                                     //帧头
            header.cmd = (UInt16)cmd;                                                   //命令
            header.len = len;                                                           //总长度

            end.crc16 = CalculateCRC16(data, (UInt32)data.Length);                      //CRC16校验
            end.end = 0xCCDD;                                                           //帧尾

            byte[] headerBytes = StructToByteArray(header);                             //将帧头结构体转为数组
            byte[] endBytes = StructToByteArray(end);                                   //将帧尾结构体转为数组
            Array.Copy(headerBytes, 0, buffer, 0, headerBytes.Length);                  //拷贝帧头到buffer
            Array.Copy(data, 0, buffer, headerBytes.Length, data.Length);               //拷贝数据到buffer
            Array.Copy(endBytes, 0, buffer,
                       headerBytes.Length + data.Length, endBytes.Length);              //拷贝帧尾到buffer

            return SerialPort_Send_data(buffer);                                        //串口发送数据
        }

        //通用接收数据函数,接收命令
        //参数:期望接收数据cmd、数据、长度、超时时间
        private STATUS GetData(CMD cmd, byte[] data, UInt32 size, int timeout)
        {
            if (data.Length < size)
            {
                labStatus.Invoke(new Action(() => { labStatus.Text = "缓冲区大小不足"; }));
                return STATUS.NOVAL;
            }

            if (!checkSerialPortConnected())                                            //如果串口关闭,则返回失败
            {
                labStatus.Invoke(new Action(() => { labStatus.Text = "串口未打开"; })); //跨进程设置状态栏信息
                return STATUS.SERIAL;
            }

            Header header = new Header();                                               //帧头
            end_t end = new end_t();                                                    //帧尾
            UInt16 len = (UInt16)(Marshal.SizeOf(typeof(Header)) +                      //总数据包长度
                                  Marshal.SizeOf(typeof(end_t)) +
                                  size);
            byte[] buffer = new byte[len];                                              //接收buffer

            header.header = 0xAABB;                                                     //帧头
            header.cmd = (UInt16)cmd;                                                   //命令
            header.len = len;                                                           //总长度

            end.crc16 = 0x00;                                                           //CRC16校验
            end.end = 0xCCDD;                                                           //帧尾

            byte[] headerBytes = StructToByteArray(header);                             //期望返回的header
            byte[] endendBytes = BitConverter.GetBytes(end.end);                        //期望返回的end->end,crc16不参与

            sp.ReadTimeout = timeout;                                                   //设置串口的超时时间
            int bytesRead = 0;                                                          //当前已读的数据长度
            try
            {
                while (bytesRead < len)                                                 //当前已读小于总长度len,则一直读
                {
                    bytesRead += sp.Read(buffer, bytesRead, len - bytesRead);           //读取剩余所需长度的数据
                    if (bytesRead >= len)                                               //已经读满所需长度数据
                    {
                        byte[] read_head = new byte[headerBytes.Length];                //读到的数据帧头
                        byte[] read_endend = new byte[endendBytes.Length];              //读到的数据帧尾
                        Array.Copy(buffer, 0, read_head, 0, read_head.Length);          //拷贝读到的数据帧头
                        Array.Copy(buffer, bytesRead - read_endend.Length,              //拷贝读到的数据帧尾
                                   read_endend, 0, read_endend.Length);
                        if (!read_head.SequenceEqual(headerBytes) ||                    //将读到的数据和期望的帧头帧尾比较
                            !read_endend.SequenceEqual(endendBytes))                    //不是期望的数据,返回错误码
                        {
                            labStatus.Invoke(new Action(() => { labStatus.Text = "读取串口，帧头帧尾错误"; }));
                            return STATUS.NOVAL;
                        }

                        //读到的数据帧头和帧尾符合预期
                        byte[] data_read = new byte[size];                              //读到的有效数据                 
                        Array.Copy(buffer, read_head.Length, data_read, 0, size);       //拷贝有效数据
                        UInt16 crc16 = CalculateCRC16(data_read, size);                 //crc16校验
                        UInt16 crc16_read = BitConverter.ToUInt16(buffer,               //读到的crc16
                                                                  bytesRead - Marshal.SizeOf(typeof(end_t)));
                        if (crc16 != crc16_read)                                        //crc16校验
                        {
                            labStatus.Invoke(new Action(() => { labStatus.Text = "读取串口，crc校验失败"; }));
                            return STATUS.CRC16;
                        }

                        Array.Copy(data_read, data, size);                              //将有效数据拷贝到传入的data
                        labStatus.Invoke(new Action(() => { labStatus.Text = "读取串口成功"; }));
                        return STATUS.OK;
                    }
                }
            }
            catch (TimeoutException)                                                    //超时报错
            {
                labStatus.Invoke(new Action(() => { labStatus.Text = "读取串口超时"; }));
                return STATUS.TIMEOUT;
            }
            catch (Exception)
            {
                labStatus.Invoke(new Action(() => { labStatus.Text = "读取串口错误"; }));
                return STATUS.NOVAL;
            }

            labStatus.Invoke(new Action(() => { labStatus.Text = "读取数据量不足"; })); //其他错误情况
            return STATUS.NOVAL;
        }

        private STATUS GetInfo_SendData()                                                 //发送获取信息的命令
        {
            byte[] buffer = new byte[0];                                                //获取信息的命令,不需要数据,定义一个空buffer
            return SendData(CMD.GETINFO, buffer);                                       //使用通用发送数据命令的函数
        }

        private STATUS EarseChip_SendData()                                               //发送全片擦除的命令
        {
            byte[] buffer = new byte[0];                                                //全片擦除的命令,不需要数据,定义一个空buffer
            return SendData(CMD.ERASE_CHIP, buffer);                                    //使用通用发送数据函数发送命令
        }

        private STATUS Earse_SendData(UInt32 addr, UInt32 len)                            //发送擦除数据的命令
        {
            //擦除数据的命令格式:addr(4) len(4)
            CmdEarse cmd = new CmdEarse                                                 //定义擦除数据命令结构体
            {
                Addr = addr,                                                            //擦除数据命令地址
                Len = len                                                               //擦除数据命令长度
            };

            byte[] buffer = StructToByteArray(cmd);                                     //将擦除命令结构体转为数组
            return SendData(CMD.ERASE, buffer);                                         //使用通用发送数据函数发送命令
        }

        private STATUS SendFile_SendData(UInt32 addr, byte[] data, UInt32 len)
        {
            HeaderSave header = new HeaderSave
            {
                Addr = addr,                                                            //发送数据命令地址
                Len = len                                                               //发送数据命令长度
            };

            byte[] headerBytes = StructToByteArray(header);                             //将发送数据命令转换成数组
                                                                                        //定义一个待发送数据buffer
            byte[] buffer = new byte[(UInt32)(Marshal.SizeOf(typeof(HeaderSave)) + len)];

            Array.Copy(headerBytes, 0, buffer, 0, headerBytes.Length);                  //将发送数据命令数组拷贝到buffer中
            Array.Copy(data, 0, buffer, headerBytes.Length, len);                       //将需要写入的数据拷贝到buffer中

            return SendData(CMD.SAVE, buffer);                                          //使用通用发送数据函数发送命令
        }

        private STATUS ReadFile_SendData(UInt32 addr, UInt32 len)                       //读取数据,发送命令
        {
            //读取数据的命令格式:addr(4) len(4)
            byte[] buffer = new byte[8];                                                //定义buffer,用于保存读取数据的命令
            byte[] array_addr = BitConverter.GetBytes((UInt32)addr);                    //将地址转换为数组,用于拼接完整的读取数据命令
            byte[] array_len = BitConverter.GetBytes((UInt32)len);                      //将长度转换为数组,用于拼接完整的读取数据命令
            Array.Copy(array_addr, 0, buffer, 0, array_addr.Length);                    //将地址数组拷贝到buffer的前4个数据
            Array.Copy(array_len, 0, buffer, 4, array_len.Length);                      //将长度数组拷贝到buffer的后4个数据

            return SendData(CMD.READ, buffer);                                          //使用通用发送数据函数发送命令
        }

        void GetInfo()
        {
            sp.DiscardInBuffer();                                                       //清除串口缓冲区
            if (GetInfo_SendData() != STATUS.OK)                                        //发送获取信息的命令
                return;

            byte[] buffer = new byte[Marshal.SizeOf<RspInfo>()];                        //定义一个和信息结构体大小的buffer,用来读取数据

            if (GetData(CMD.GETINFO, buffer, (UInt32)buffer.Length, 1000) != STATUS.OK) //读取MCU的返回数据,最大1000ms超时
            {
                MessageBox.Show("读取芯片信息失败");                                    //读取数据失败,弹窗提示
                return;                                                                 //返回
            }

            RspInfo info = ByteArrayToStruct<RspInfo>(buffer);                          //将读取到的数据转换成rsp_info_t结构体

            txtChipID.Invoke(new Action<UInt32>(n =>                                    //将ID转为字符串,显示到UI界面
            {
                txtChipID.Text = "0x" + n.ToString("X2");
            }), info.Id);

            txtChipSize.Invoke(new Action<UInt32>(n =>                                  //将SIZE转为字符串,显示到UI界面
            {
                txtChipSize.Text = "0x" + n.ToString("X2");
            }), info.Size);
            MessageBox.Show("读取芯片信息成功");                                        //读取信息成功,弹窗提醒
        }

        void EarseChip()
        {
            sp.DiscardInBuffer();                                                       //清除串口缓冲区
            if (EarseChip_SendData() != STATUS.OK)                                      //发送全片擦除的命令
                return;

            RspStatus rsp = new RspStatus();                                            //响应数据结构体
            byte[] buffer = new byte[Marshal.SizeOf<RspStatus>()];                      //定义一个和响应数据大小相同的buffer,用于接收数据

            if (GetData(CMD.ERASE_CHIP, buffer, (UInt32)buffer.Length, -1) != STATUS.OK)//擦除大概需要21秒左右,需要一直等待,或者超时时间长一点
            {
                MessageBox.Show("整片擦除失败");                                        //获取擦除数据返回值失败,弹窗提醒
                return;
            }

            rsp.Addr = BitConverter.ToUInt32(buffer, 0);                                //buffer的前4个数据转换成响应数据的addr
            rsp.Status = BitConverter.ToUInt32(buffer, 4);                              //buffer的后4个数据转换成响应数据的状态
            if (rsp.Status != (UInt32)RESULT.OK)                                        //如果返回的状态不是OK,弹窗提醒
            {
                MessageBox.Show("整片擦除失败");                                        //弹窗提醒擦除失败
                return;
            }

            MessageBox.Show("整片擦除成功");                                            //弹窗提醒,擦除成功
        }

        void EarseData()
        {
            UInt32 addr = String2UInt32(txtStart.Text);                                 //擦除数据的起始位置
            UInt32 len = String2UInt32(txtLength.Text);                                 //擦除数据的长度

            sp.DiscardInBuffer();                                                       //清除串口缓冲区
            if (Earse_SendData(addr, len) != STATUS.OK)                                 //发送擦除数据的命令
                return;

            RspStatus rsp = new RspStatus();                                            //响应数据结构体
            byte[] buffer = new byte[Marshal.SizeOf<RspStatus>()];                      //定义一个和响应数据大小相同的buffer

            if (GetData(CMD.ERASE, buffer, (UInt32)buffer.Length, -1) != STATUS.OK)      //擦除大概需要21秒左右,需要一直等待,或者超时时间长一点
            {
                MessageBox.Show("数据擦除失败");                                        //获取擦除数据返回值失败,弹窗提醒
                return;
            }

            rsp.Addr = BitConverter.ToUInt32(buffer, 0);                                //buffer的前4个数据转换成响应数据的addr
            rsp.Status = BitConverter.ToUInt32(buffer, 4);                              //buffer的后4个数据转换成响应数据的状态
            if (rsp.Status != (UInt32)RESULT.OK)                                        //如果返回的状态不是OK,弹窗提醒
            {
                MessageBox.Show("数据擦除失败");                                        //弹窗提醒擦除失败
                return;
            }

            MessageBox.Show("数据擦除成功");                                            //弹窗提醒,擦除成功
        }

        private void SaveFile()
        {
            string filePath = txtFileSave.Text;                                          //保存的文件路径
            UInt32 addr = String2UInt32(txtStart.Text);                                 //保存数据的起始位置
            UInt32 size_1 = String2UInt32(txtLength.Text);                              //保存数据的长度

            byte[] data = new byte[4096];                                               //数据缓冲区,每次发送4096字节
            byte[] buffer = new byte[Marshal.SizeOf<RspStatus>()];                      //返回数据缓冲区,接收MCU返回的数据,用于判断MCU是否成功保存

            if (string.IsNullOrWhiteSpace(filePath))                                    //文件路径为空,直接返回
                return;

            try
            {
                //读取文件流
                using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
                {
                    UInt32 len = 0;                                                     //当前已发送的长度
                    UInt32 size = Math.Min((UInt32)fileStream.Length, size_1);          //文件总长度,在用户界面输入文件长度和文件长度中取较小值
                    UInt32 bytesRead = 0;                                               //每次实际读取的数据长度
                    UInt32 retry = 0;                                                   //失败后重试次数计数
                    while (len < size)
                    {
                        sp.DiscardInBuffer();                                           //清除串口缓冲区
                                                                                        //每次实际读取的数据长度为,尝试读取(数据缓冲区的长度,和当前剩余的数据长度中取较小值)长度的数据的返回值
                        bytesRead = (UInt32)fileStream.Read(data, 0, (int)Math.Min((UInt32)data.Length, size - len));
                        if (SendFile_SendData(addr + len, data, bytesRead) != STATUS.OK)            //发送读取数据的命令,如果发送失败,只能是串口异常，直接返回
                            break;

                        //获取数据,超时时间2000,一直等待
                        if (GetData(CMD.SAVE, buffer, (UInt32)buffer.Length, 1000) != STATUS.OK)
                        {
                            retry++;                                                    //重试次数加1
                            if (retry > MAX_RETRY)                                      //超过最大重试次数,失败
                                break;
                            continue;                                                   //重试
                        }
                        RspStatus rsp = ByteArrayToStruct<RspStatus>(buffer);     //将返回的数据转换为结构体
                        if (rsp.Status != (UInt32)RESULT.OK)                            //如果状态不为OK,则需要重试
                        {
                            retry++;                                                    //重试次数加1
                            if (retry > MAX_RETRY)                                      //超过最大重试次数,失败
                                break;
                            continue;                                                   //重试
                        }

                        retry = 0;                                                      //发送成功,重试次数清零
                        len += bytesRead;                                               //已发送数据累加

                        //显示进度条,跨进程访问
                        prbSend.Invoke(new Action<int>(n => { prbSend.Value = n; }), (int)(len * 1000 / size));
                    }
                    if (len < size)
                        MessageBox.Show("数据发送失败: " + len + "/" + size + "字节");  //发送数据失败,弹窗提醒
                    else
                        MessageBox.Show("数据发送完成: " + len + "/" + size + "字节");  //发送数据成功,弹窗提醒
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("异常: " + ex.Message);                               //读取文件错误,弹窗提醒
                return;
            }
        }

        void ReadFile()                                                                 //从MCU的FLASH读取数据
        {
            UInt32 addr = String2UInt32(txtStart.Text);                                 //读取数据的起始位置
            UInt32 size = String2UInt32(txtLength.Text);                                //读取数据的长度

            string filePath = txtFileRead.Text;                                         //保存的文件路径
            if (!Directory.Exists(Path.GetDirectoryName(filePath)))                     //判断保存的文件路径所在文件夹是否存在
            {
                MessageBox.Show("路径不存在: " + filePath);                             //不存在,弹窗提醒后,直接退出
                return;
            }

            UInt32 retry = 0;                                                           //重试次数计数
            byte[] buffer = new byte[4096];                                             //接收buffer

            try
            {
                //创建文件流用于写入数据
                using (FileStream fileStream = new FileStream(filePath, FileMode.Create))
                {
                    UInt32 len = 0;                                                     //已读取数据的总长度
                    while (len < size)
                    {
                        UInt32 bytesRead = (UInt32)Math.Min(buffer.Length, size - len); //本次需要读取的数据
                        sp.DiscardInBuffer();                                           //清除串口缓冲区
                        if (ReadFile_SendData(addr + len, bytesRead) != STATUS.OK)                  //发送读数据的命令
                            break;

                        STATUS err = GetData(CMD.READ, buffer, bytesRead, 1000);         //读取MCU返回的数据,超时时间1000ms
                        if (err != STATUS.OK)                                            //接收数据失败,重试 
                        {
                            if (err == STATUS.SERIAL)                                    //串口错误,直接返回,不再重试
                                break;
                            retry++;                                                    //重试次数加1
                            if (retry > MAX_RETRY)                                      //重试次数超过最大次数,退出
                                break;
                            Thread.Sleep(2);                                            //延时2ms,给MCU缓冲下
                            continue;
                        }

                        retry = 0;                                                      //重试次数归零
                        fileStream.Write(buffer, 0, (int)bytesRead);                    //将已读取的数据,写到文件中
                        len += bytesRead;                                               //已读的数据累加

                        //显示进度条,跨进程访问
                        prbRead.Invoke(new Action<int>(n => { if (n >= 1000) n = 999; prbRead.Value = n; }), (int)(len * 1000 / size));
                        Thread.Sleep(2);                                                //延时2ms,给MCU缓冲下
                    }
                    if (len < size)
                        MessageBox.Show("数据读取失败: " + len + "/" + size + "字节");  //弹窗显示失败,已读取的字节数
                    else
                        MessageBox.Show("数据接收完成: " + len + "/" + size + "字节");  //弹窗显示成功,及已读取的字节数
                    Process.Start("explorer.exe", Path.GetDirectoryName(filePath));     //在资源管理器中打开文件夹
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("异常: " + ex);                                         //弹窗显示失败信息
            }
        }

        private void Thread_Task(object obj)                                            //工作线程,线程中判断避免循环进入
        {
            thread_running = true;                                                      //线程运行中标志判断,开启线程先判断标志,避免重复创建
            CMD cmd = (CMD)obj;                                                         //参数转为CMD,根据CMD执行不同的任务
            switch (cmd)
            {
                case CMD.GETINFO:
                    GetInfo();                                                          //获取信息
                    break;
                case CMD.ERASE_CHIP:
                    EarseChip();                                                        //整片擦除
                    break;
                case CMD.ERASE:
                    EarseData();                                                        //擦除指定地址和长度的数据
                    break;
                case CMD.SAVE:
                    SaveFile();                                                         //发送数据给MCU保存
                    break;
                case CMD.READ:
                    ReadFile();                                                         //从MCU读取数据
                    break;
                default:
                    break;
            }
            thread_running = false;                                                     //线程运行标志清除
        }

        private void btnGetInfo_Click(object sender, EventArgs e)                       //获取信息
        {
            if (!checkSerialPortConnected())                                            //检查串口是否处于打开状态
                return;

            if (thread_running == true)                                                 //如果线程处于运行中,表示有任务正在执行,直接返回
                return;

            Thread thread = new Thread(new ParameterizedThreadStart(Thread_Task));      //创建一个线程,用于和MCU通信,避免主线程卡死
            thread.Start(CMD.GETINFO);                                                  //启动线程,传入指令
        }

        private void btnEraseChip_Click(object sender, EventArgs e)                     //整片擦除
        {
            if (!checkSerialPortConnected())                                            //检查串口是否处于打开状态
                return;

            if (thread_running == true)                                                 //如果线程处于运行中,表示有任务正在执行,直接返回
                return;

            Thread thread = new Thread(new ParameterizedThreadStart(Thread_Task));      //创建一个线程,用于和MCU通信,避免主线程卡死
            thread.Start(CMD.ERASE_CHIP);                                               //启动线程,传入指令
        }

        private void btnEarseData_Click(object sender, EventArgs e)                     //擦除指定地址和长度的数据
        {
            if (!checkSerialPortConnected())                                            //检查串口是否处于打开状态
                return;

            if (thread_running == true)                                                 //如果线程处于运行中,表示有任务正在执行,直接返回
                return;

            Thread thread = new Thread(new ParameterizedThreadStart(Thread_Task));      //创建一个线程,用于和MCU通信,避免主线程卡死
            thread.Start(CMD.ERASE);                                                    //启动线程,传入指令
        }

        private void btnSend_Click(object sender, EventArgs e)                          //发送数据
        {
            if (!checkSerialPortConnected())                                            //检查串口是否处于打开状态
                return;

            if (thread_running == true)                                                 //如果线程处于运行中,表示有任务正在执行,直接返回
                return;

            Thread thread = new Thread(new ParameterizedThreadStart(Thread_Task));      //创建一个线程,用于和MCU通信,避免主线程卡死
            thread.Start(CMD.SAVE);                                                     //启动线程,传入指令
        }

        private void btnRead_Click(object sender, EventArgs e)                          //读取数据
        {
            if (!checkSerialPortConnected())                                            //检查串口是否处于打开状态
                return;

            if (thread_running == true)                                                 //如果线程处于运行中,表示有任务正在执行,直接返回
                return;

            Thread thread = new Thread(new ParameterizedThreadStart(Thread_Task));      //创建一个线程,用于和MCU通信,避免主线程卡死
            thread.Start(CMD.READ);                                                     //启动线程,传入指令
        }
    }
}